/*
 * Copyright (c) [2012] - [2017] Red Hat, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Red Hat, Inc. - initial API and implementation
 */
package com.codenvy.service.system;

import static java.util.Arrays.deepToString;
import static org.eclipse.che.commons.lang.Size.parseSize;

import com.google.inject.Inject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.plugin.docker.client.DockerConnector;
import org.eclipse.che.plugin.docker.client.DockerConnectorProvider;
import org.eclipse.che.plugin.docker.client.json.SystemInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implementation of {@link SystemRamInfoProvider} based on docker.
 *
 * @author Igor Vinokur
 */
public class DockerBasedSystemRamInfoProvider implements SystemRamInfoProvider {

  private static final Logger LOG = LoggerFactory.getLogger(DockerBasedSystemRamInfoProvider.class);

  private static final String SYSTEM_RAM_INFO_ERROR =
      "An error occurred while getting system RAM info.";

  private final DockerConnector dockerConnector;

  @Inject
  public DockerBasedSystemRamInfoProvider(DockerConnectorProvider dockerConnectorProvider) {
    this.dockerConnector = dockerConnectorProvider.get();
  }

  @Override
  public SystemRamInfo getSystemRamInfo() throws ServerException {
    SystemInfo systemInfo;
    try {
      systemInfo = dockerConnector.getSystemInfo();
    } catch (IOException e) {
      LOG.error("Failed to retrieve system information from docker.", e);
      throw new ServerException(SYSTEM_RAM_INFO_ERROR, e);
    }
    String[][] driverStatus = systemInfo.getDriverStatus();
    String[][] systemStatus = systemInfo.getSystemStatus();
    String[][] statusOutput = driverStatus == null ? systemStatus : driverStatus;
    if (statusOutput == null) {
      LOG.error(
          "Empty system information was received from docker. Whole system info from docker {}",
          systemInfo);
      throw new ServerException(SYSTEM_RAM_INFO_ERROR);
    }

    List<String> allNodesRamUsage = new ArrayList<>();
    /*
     System values from docker response are introduced in a 2-dimensional string array e.g.:
     "SystemStatus": [
        .
        .
        .
        " node1.<host>",
        "<host>:<port>"
        ],
        "  └ Reserved Memory",
        "1000 MiB / 5.881 GiB"
        ],
        .
        .
        .
        " node2.<host>",
        "<host>:<port>"
        ],
        [
        "  └ Reserved Memory",
        "1000 MiB / 1.511 GiB"
        ],
        .
        .
        .
     ],
     RAM vales are divided by nodes and introduced in a string array which has '└ Reserved Memory' in first element,
     and RAM values of node in format <used RAM size> / <total RAM size> in second element.
     There are as many RAM values arrays inside the 'statusOutput' array as many nodes are present in the system.
    */
    for (String[] systemInfoEntry : statusOutput) {
      if (systemInfoEntry.length == 2
          && systemInfoEntry[0] != null
          && systemInfoEntry[0].endsWith("Reserved Memory")) {
        allNodesRamUsage.add(systemInfoEntry[1]);
      }
    }
    if (allNodesRamUsage.isEmpty()) {
      LOG.error(
          "System RAM values was not found in docker system info response. All system values from docker: {}",
          deepToString(statusOutput));
      throw new ServerException(SYSTEM_RAM_INFO_ERROR);
    }

    long systemRamUsed = 0;
    long systemRamTotal = 0;
    for (String nodeRamUsage : allNodesRamUsage) {
      String[] ramValues = nodeRamUsage.split(" / ");
      if (ramValues.length != 2) {
        LOG.error(
            "A problem occurred while parsing system information from docker. "
                + "Expected: <used RAM size> / <total RAM size> but got: "
                + nodeRamUsage);
        throw new ServerException(SYSTEM_RAM_INFO_ERROR);
      }
      systemRamUsed += parseSize(ramValues[0]);
      systemRamTotal += parseSize(ramValues[1]);
    }

    return new SystemRamInfo(systemRamUsed, systemRamTotal);
  }
}
